<?php

use Workerman\Worker;
use Workerman\Timer;
use PHPSocketIO\SocketIO;
use Workerman\Protocols\Http\Request;
use Workerman\Connection\TcpConnection;
use Dershun\PhpRedis\RedisString;


include __DIR__ . '/vendor/autoload.php';

// 全局数组保存uid在线数据
$uidConnectionMap = array();
// 记录最后一次广播的在线用户数
$last_online_count = 0;
// 记录最后一次广播的在线页面数
$last_online_page_count = 0;

$config = [
    'host' => '127.0.0.1',
    'port' => 6379,
    'password' => '123456',
    'select' => 0,
    'timeout' => 0,
    'expire' => 0,
    'persistent' => false,
    'prefix' => ''
];
$redis = new RedisString($config);

// PHPSocketIO服务
$sender_io = new SocketIO(2120);

// 客户端发起连接事件时，设置连接socket的各种事件回调
$sender_io->on('connection', function ($socket) use ($sender_io, $redis) {
    // 当客户端发来登录事件时触发
    $socket->on('login', function ($uid) use ($socket) {
        global $uidConnectionMap, $last_online_count, $last_online_page_count;
        // 已经登录过了
        if (isset($socket->uid)) {
            return;
        }
        // 更新对应uid的在线数据
        $uid = (string)$uid;
        if (!isset($uidConnectionMap[$uid])) {
            $uidConnectionMap[$uid] = 0;
        }
        // 这个uid有++$uidConnectionMap[$uid]个socket连接
        ++$uidConnectionMap[$uid];
        // 将这个连接加入到uid分组，方便针对uid推送数据
        $socket->join($uid);
        $socket->uid = $uid;
        // 更新这个socket对应页面的在线数据
        $socket->emit('update_online_count', "当前<b>{$last_online_count}</b>人在线");
    });

    // 当客户端断开连接是触发（一般是关闭网页或者跳转刷新导致）
    $socket->on('disconnect', function () use ($socket) {
        if (!isset($socket->uid)) {
            return;
        }
        global $uidConnectionMap, $sender_io;
        // 将uid的在线socket数减一
        if (--$uidConnectionMap[$socket->uid] <= 0) {
            unset($uidConnectionMap[$socket->uid]);
        }
    });
});

// 当$sender_io启动后监听一个http端口，通过这个端口可以给任意uid或者所有uid推送数据
$sender_io->on('workerStart', function () use ($redis) {
    // 监听一个http端口
    $inner_http_worker = new Worker('http://0.0.0.0:2121');
    // 当http客户端发来数据时触发
    $inner_http_worker->onMessage = function (TcpConnection $http_connection, Request $request) use ($redis) {
        global $uidConnectionMap;
        $post = $request->post();
        $post = $post ? $post : $request->get();
        // 推送数据的url格式 type=publish&to=uid&content=xxxx
        switch (@$post['type']) {
            case 'publish':
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('new_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('new_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '推送成功']));
                }
            case 'technician': // 技师位置更新
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('technician_location_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('technician_location_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '推送成功']));
                }
            case 'apply_technician': // 用户申请技师
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('apply_technician_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('apply_technician_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '推送成功']));
                }
            case 'apply_store': // 店铺入驻推送
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('apply_store_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('apply_store_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '推送成功']));
                }
            case 'return_order': // 有订单退款
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('return_order_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('return_order_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '推送成功']));
                }
            case 'new_order': // 有新订单
                global $sender_io;
                $to = @$post['to'];
                $post['content'] = htmlspecialchars(@$post['content']);
                // 有指定uid则向uid所在socket组发送数据
                if ($to) {
                    $sender_io->to($to)->emit('new_order_msg', $post['content']);
                    // 否则向所有uid推送数据
                } else {
                    $sender_io->emit('new_order_msg', @$post['content']);
                }
                // http接口返回，如果用户离线socket返回fail
                if ($to && !isset($uidConnectionMap[$to])) {
                    return $http_connection->send(json_encode(['status' => 2, 'msg' => '推送失败11']));
                } else {
                    return $http_connection->send(json_encode(['status' => 1, 'msg' => '有新订单,推送成功']));
                }
        }
        return $http_connection->send('fail');
    };
    // 执行监听
    $inner_http_worker->listen();

    // 一个定时器，定时向所有uid推送当前uid在线数及在线页面数
    Timer::add(1, function () {
        global $uidConnectionMap, $sender_io, $last_online_count, $last_online_page_count;
        $online_count_now = count($uidConnectionMap);
        $online_page_count_now = array_sum($uidConnectionMap);
        // 只有在客户端在线数变化了才广播，减少不必要的客户端通讯
        if ($last_online_count != $online_count_now || $last_online_page_count != $online_page_count_now) {
            $sender_io->emit('update_online_count', "当前<b>{$online_count_now}</b>人在线");
            $last_online_count = $online_count_now;
            $last_online_page_count = $online_page_count_now;
        }
    });
});

if (!defined('GLOBAL_START')) {
    Worker::runAll();
}
